package com.zijin.service.user.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zijin.constant.*;
import com.zijin.context.BaseContext;
import com.zijin.enumeration.LockStatusEnum;
import com.zijin.dto.user.UserDTO;
import com.zijin.dto.user.UserEidtDTO;
import com.zijin.dto.user.UserLoginDTO;
import com.zijin.dto.user.UserSignUpDto;
import com.zijin.entity.user.User;
import com.zijin.enumeration.SexEnum;
import com.zijin.exception.AccountException;
import com.zijin.exception.BaseException;
import com.zijin.properties.JwtProperties;
import com.zijin.qurey.user.UserPageQureyDTO;
import com.zijin.service.user.IRolePathService;
import com.zijin.service.user.IRoleService;
import com.zijin.service.user.IUserRoleService;
import com.zijin.utils.EmailSenderUtil;
import com.zijin.utils.JwtUtil;
import com.zijin.utils.MD5Util;

import com.zijin.mapper.UserMapper;
import com.zijin.service.user.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zijin.vo.user.UserLoginVO;
import com.zijin.vo.user.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author JWei Xu
 * @since 2025-02-01
 */

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    JwtProperties jwtProperties;

    @Autowired
    RedisTemplate redisTemplate;

    @Resource
    IRolePathService rolePathService;

    @Resource
    IUserRoleService userRoleService;

    @Resource
    IRoleService roleService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserLoginVO login(UserLoginDTO userLoginDTO, HttpServletRequest request) throws LoginException {
        userLoginDTO .setPassword(MD5Util.md5(userLoginDTO.getPassword()));
        List<User> users = this.lambdaQuery()
                .eq(User::getUserName, userLoginDTO.getUserName())
                .eq(User::getPassword, userLoginDTO.getPassword())
                .ne(User::getStatus, LockStatusEnum.LOCK)
                .list();
        //验证账号和密码，但是后台唯一不变的是id，以id为准，所以将id做为用户操作凭证
        if(ObjectUtil.isEmpty(users)){
            throw new BaseException(MessageConstant.PASSWORD_ERROR);
        }
        User user = users.get(0);


        //登录成功后，生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID, user.getId());
        Set<String> roleKeys = roleService.getRoleNameByUserId(user.getId());
        claims.put(JwtClaimsConstant.IDENTITY, roleKeys);
        claims.put(JwtClaimsConstant.USERNAME, user.getNickName());


        String ip = this.getIp(request);
        user.setLoginIp(ip);
        user.setLoginDate(LocalDateTime.now());
        this.updateById(user);
        String token = JwtUtil.createJWT(
                jwtProperties.getUserSecretKey(),
                jwtProperties.getUserTtl(),
                claims);


        Set<String> allowedRoutes = rolePathService.getAllowedRoutes(user.getId());
        UserLoginVO employeeLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .userName(user.getUserName())
                .name(user.getNickName())
                .allowedRoutes(allowedRoutes)
                .token(token)
                .build();

        return employeeLoginVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOne(UserDTO userDTO) {
        ValueOperations<String,String> valueOperations = redisTemplate.opsForValue();
        String uuid = UUID.randomUUID().toString();
        String key = "insertUser::"+userDTO.getUserName();
        //两秒时间锁定
        boolean flage = valueOperations.setIfAbsent(key, uuid, 2, TimeUnit.SECONDS);
        if (flage) {

            List<User> users = this.lambdaQuery()
                    .eq(User::getUserName, userDTO.getUserName())
                    .list();
            if (ObjectUtil.isNotEmpty(users)) {
                throw new AccountException("账号重复");
            }
            if(ObjectUtil.isEmpty(userDTO.getPassword())){
                userDTO.setPassword(BusinessContant.DEFAULT_PASSWORD);
            }
            User user = User.builder()
                    .nickName(userDTO.getNickName())
                    .userName(userDTO.getUserName())
                    .password(MD5Util.md5(userDTO.getPassword()))
                    .avatar(AvatarConstant.getAvatar())
                    .sex(SexEnum.CONFIDENTIALITY)
                    .build();
            this.save(user);
            //添加默认的规则
            userRoleService.addDefaultRole(user.getId());
            String getUuid = valueOperations.get(key);
            if (ObjectUtil.equal(getUuid, uuid)) {
                redisTemplate.delete(key);
            }
        } else {
            throw new AccountException("对不起！被抢先一步注册啦！");
        }
    }

    @Override
    public IPage<UserVo> pageQurey(UserPageQureyDTO userPageQureyDTO) {
        Page<User> page = this.lambdaQuery()
                .ne(User::getId,BaseContext.getCurrentId())
                .ne(User::getUserName,"admin")
                .or(ObjectUtil.isNotEmpty(userPageQureyDTO.getSearch()),w -> w.eq( User::getRemark, userPageQureyDTO.getSearch()))
                .or(ObjectUtil.isNotEmpty(userPageQureyDTO.getSearch()),w -> w.eq( User::getNickName, userPageQureyDTO.getSearch()))
                .or(ObjectUtil.isNotEmpty(userPageQureyDTO.getSearch()),w -> w.eq( User::getUserName, userPageQureyDTO.getSearch()))
                .page(new Page<>(userPageQureyDTO.getPageNum(), userPageQureyDTO.getPageSize()));
        return page.convert(u->{
            return BeanUtil.copyProperties(u,UserVo.class);
        });
    }

    @Override
    public Boolean editOne(Long id, UserEidtDTO userDTO) {
        if(ObjectUtil.isEmpty(this.getById(id))){
            throw new BaseException("数据不存在!");
        };
        User user = BeanUtil.copyProperties(userDTO, User.class);
        user.setId(id);
        user.setUserName(null);
        user.setSex(SexEnum.getEnum(userDTO.getSex()));
        if(ObjectUtil.isNotEmpty(userDTO.getPassword())){
            user.setPassword(MD5Util.md5(userDTO.getPassword()));
        }
        return this.updateById(user);
    }

    @Override
    public void checkUser(String userName) {
        if(ObjectUtil.isNotEmpty(this.lambdaQuery().eq(User::getUserName,userName).list())){
            throw new BaseException(ErrorMsgConstant.DUPLICATE_ACCOUNTS);
        }
    }

    @Override
    public void registration(UserSignUpDto dto) {
        //检查验证码
        String checkCodeId = CaptchaConstant.PREFIX_KEY + dto.getCheckCodeId();

        ValueOperations valueOperations = redisTemplate.opsForValue();

        String checkCode = String.valueOf( valueOperations.get(checkCodeId));
        if(ObjectUtil.isNotEmpty(checkCode)){
            redisTemplate.delete(checkCode);
        }else {
            throw new BaseException(ErrorMsgConstant.CAPTCHA_ERROR);
        }

        if(ObjectUtil.notEqual(dto.getCheckCode(),checkCode)){
            throw new BaseException(ErrorMsgConstant.CAPTCHA_ERROR);
        }

        //检查邮箱验证码
        String emailCheckCodeId  = CaptchaConstant.EMAIL_CHECKCODE+dto.getUserName();
        String eamilCheckCode = String.valueOf( valueOperations.get(emailCheckCodeId));
        if(ObjectUtil.isNotEmpty(eamilCheckCode)){
            redisTemplate.delete(emailCheckCodeId);
        }else {
            throw new BaseException(ErrorMsgConstant.CAPTCHA_ERROR);
        }
        if(ObjectUtil.notEqual(eamilCheckCode,dto.getEmailCheckCode())){
            throw new BaseException(ErrorMsgConstant.CAPTCHA_ERROR);
        };

        //saveOne
        this.saveOne(dto);
    }

    @Override
    public void sendEmail(String email) {
        if(!email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")){
            throw new BaseException(ErrorMsgConstant.ERROR_EMAIL);
        };
        Integer checkCode = (int) (Math.random() * 9000) + 1000;
        String formattedMessage = StrUtil.format("【ZiJinBase】验证码{}，用于创建账号登录，5分钟内有效。验证码提供给他人可能导致账号被盗，请勿泄露，谨防被骗。", checkCode);
        ValueOperations valueOperations = redisTemplate.opsForValue();
        valueOperations.set(CaptchaConstant.EMAIL_CHECKCODE+email,checkCode,300,TimeUnit.SECONDS);
        EmailSenderUtil.postMessage("注册验证码【ZiJinBase】",formattedMessage,email);
    }

    @Override
    public UserVo getUserById(Long id) {
        List<UserVo> one = baseMapper.getOne(id);
        return one.get(one.size()-1);
    }

    @Override
    public UserVo getUser() {
        Long userId = BaseContext.getCurrentId();
        return this.getUserById(userId);
    }

    @Override
    public Boolean edit(UserEidtDTO userDTO) {
        Long userId = BaseContext.getCurrentId();
        UserEidtDTO userDto = new UserEidtDTO();
        //仅允许修改这三个值
        userDto.setAvatar(userDTO.getAvatar());
        userDto.setNickName(userDto.getNickName());
        userDto.setSex(userDTO.getSex());
        return this.editOne(userId,userDto);
    }

    @Override
    public List<UserVo> getList(UserPageQureyDTO userPageQureyDTO) {
        return BeanUtil.copyToList(this.lambdaQuery()
                .or(ObjectUtil.isNotEmpty(userPageQureyDTO.getSearch()),w -> w.eq( User::getRemark, userPageQureyDTO.getSearch()))
                .or(ObjectUtil.isNotEmpty(userPageQureyDTO.getSearch()),w -> w.eq( User::getNickName, userPageQureyDTO.getSearch()))
                .or(ObjectUtil.isNotEmpty(userPageQureyDTO.getSearch()),w -> w.eq( User::getUserName, userPageQureyDTO.getSearch()))
                .list(),UserVo.class);

    }

    @Override
    public Boolean resetPassword(Long id) {
        return this.lambdaUpdate().eq(User::getId,id).set(User::getPassword,MD5Util.md5(BusinessContant.DEFAULT_PASSWORD)).update();
    }

    public String getIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if(ObjectUtil.isEmpty(ip)){
            ip = "unknown";
        }
        return ip;
    }
}
